#include "main.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_flash.h"
#include "esp_timer.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "driver/gpio.h"
#include "freertos/event_groups.h"
#include "nvs_flash.h"
#include "esp_sntp.h"
#include "time.h"
#include "esp_task_wdt.h"
#include "bsp_hx711.h"
#include "mqtt_client.h"
#include "cJSON.h"

uint8_t data_block_num = 16;

/* 卡的ID存储，32位,4字节 */
u8 ucArray_ID[4];
uint8_t ucStatusReturn; // 返回状态

extern float Temp;
extern float Humi;
extern const char *TAG_STA;
extern const char *TAG;
extern EventGroupHandle_t s_wifi_event_group;

// 引用mqtt句柄
extern esp_mqtt_client_handle_t client;

// 片选引脚
extern uint8_t cs;

SemaphoreHandle_t xSemaphore;
SemaphoreHandle_t xSemaphore1;
SemaphoreHandle_t xSemaphore2;
SemaphoreHandle_t xSemaphore3;

int i = 0;

uint8_t read_write_data[16] = {0}; // 读写数据缓存
uint8_t random_number, computed_hash;
uint8_t tag_ID[4];

int16_t weight; // 称重值

uint8_t time_arry[16] = {0};
char *cur_time = "";
char time_str[17]; // 为了确保'\0'结尾，多预留一个字符



char publish_message[100];

void app_main(void)
{
    if (wifi_proc() == ESP_OK)
    {
        printf("wifi init ok\r\n");
        ESP_LOGI(TAG, "[APP] Startup..");
        ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size());
        ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());

        mqtt_app_start();
    }
    else
    {
        printf("wifi init fail\r\n");
    }
    // 同步时间
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
    esp_sntp_setservername(0, "ntp.aliyun.com");

    esp_sntp_init();
    setenv("TZ", "CST-8", 1);
    tzset();

    printf("RC522 Init\r\n");

    // 初始化两个RC522模块
    cs = 1;
    RC522_Init();
    RC522_Rese();

    printf("RC522 Start\r\n");

    xTaskCreate(rc522_task, "rc522_task", 4096, NULL, 10, NULL);
    xTaskCreate(temp_humi_task, "temp_humi_task", 4096, NULL, 5, NULL);
    // xTaskCreate(hx711_task, "hx711_task", 4096, NULL, 5, NULL);
}

void rc522_task(void *pvParameters)
{
    HX711_GPIO_Init();

    Get_Maopi(); // 称毛皮重量
    delay_ms(500);
    Get_Maopi(); // 重新获取毛皮重量
    while (1)
    {
        if (cs == 1) // 饮食区
        {
            data_block_num = 20; // 扇区6 存放饮水区时间与进食区时间 块1 块2 20 21 22 23
            cur_time = get_current_time();
            RC522_CS2_Disable(); // 确保禁用另一个RC522芯片
            RC522_CS3_Disable();
            RC522_CS1_Enable();                  // 启用当前RC522芯片
            vTaskDelay(10 / portTICK_PERIOD_MS); // 添加适当延时以确保片选信号稳定
            rc522_proc();
            RC522_CS1_Disable(); // 完成操作后禁用当前RC522芯片
            cs++;
            // printf("time1 = %s\r\n",cur_time);
        }
        else if (cs == 2) // 刷卡区
        {
            RC522_CS1_Disable(); // 确保禁用另一个RC522芯片
            RC522_CS3_Disable();
            RC522_CS2_Enable(); // 启用当前RC522芯片
            data_block_num = 24;
            vTaskDelay(10 / portTICK_PERIOD_MS); // 添加适当延时以确保片选信号稳定
            rc522_proc2();
            RC522_CS2_Disable(); // 完成操作后禁用当前RC522芯片
            cs++;
        }
        else if (cs == 3) // 出入口
        {
            RC522_CS1_Disable(); // 确保禁用另一个RC522芯片
            RC522_CS2_Disable();
            RC522_CS3_Enable();                  // 启用当前RC522芯片
            data_block_num = 16;                 // 扇区5 存放出入口时间以及体重数据 16 17 18 19
            vTaskDelay(10 / portTICK_PERIOD_MS); // 添加适当延时以确保片选信号稳定
            rc522_proc();
            RC522_CS3_Disable(); // 完成操作后禁用当前RC522芯片
            cs = 1;
        }
        vTaskDelay(50 / portTICK_PERIOD_MS); // 延时50ms
    }
}

void temp_humi_task(void *pvParameters)
{
    while (1)
    {
        // 读取温湿度
        DHT11();
        // 显示读取后的温度数据
        // printf("temperature = %.2f\r\n", Temp);
        // // 显示读取后的湿度数据
        // printf("humidity = %.2f\r\n", Humi);
        vTaskDelay(60000 / portTICK_PERIOD_MS);
        // 创建一个JSON对象
        cJSON *json = cJSON_CreateObject();
        sprintf(card_id, "%X%X%X%X", ucArray_ID[0], ucArray_ID[1], ucArray_ID[2], ucArray_ID[3]);
        // 添加键值对到JSON对象
        cJSON_AddNumberToObject(json, "temperature", Temp);
        cJSON_AddNumberToObject(json, "humidity", Humi);
        cJSON_AddStringToObject(json, "timestamp", cur_time);

        // 将JSON对象转换为字符串
        char *json_string = cJSON_Print(json);
        // 传输动物体重以及进入时间
        printf("publish_message: %s\r\n", json_string);
        esp_mqtt_client_publish(client, "/env_data", json_string, strlen(json_string), 2, 0);
    }
}

void hx711_task(void *pvParameters)
{
    HX711_GPIO_Init();

    Get_Maopi(); // 称毛皮重量
    delay_ms(500);
    Get_Maopi(); // 重新获取毛皮重量

    printf("start..........\r\n");
    while (1)
    {
        printf("w = %.2fg\r\n", Get_Weight());
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

/*
    扇区5 存放出入口时间以及体重数据 16 17 18 19
    扇区6 存放饮水区时间与进食区时间 块1 块2 20 21 22 23
    扇区7 存放动物信息 年龄 种类 24 25 26 27
    16体重 17出入口时间 20饮水时间 21进食时间 24年龄 25种类
*/

void rc522_proc()
{
    // 初始化状态
    uint8_t ucStatusReturn = MI_ERR;
    // char *card_id = malloc(50);
    // char *eat_time = malloc(30);        //饮食时间
    // char *io_time = malloc(30);         //出入口时间

    // 尝试多次寻卡
    for (int attempt = 0; attempt < 3; attempt++)
    {
        ucStatusReturn = PcdRequest(PICC_REQALL, ucArray_ID);
        if (ucStatusReturn == MI_OK)
            break;
    }

    if (ucStatusReturn != MI_OK)
    {
        // printf("PcdRequest failure\r\n");
        RC522_Rese(); // 复位RC522
        return;
    }

    // 防冲突操作
    if (PcdAnticoll(ucArray_ID) == MI_OK)
    {
        // 输出卡ID
        printf("ID: %X %X %X %X\r\n", ucArray_ID[0], ucArray_ID[1], ucArray_ID[2], ucArray_ID[3]);
        // 选卡
        if (PcdSelect(ucArray_ID) != MI_OK)
        {
            printf("PcdSelect failure\r\n");
            return;
        }

        // 校验卡片密码
        if (PcdAuthState(PICC_AUTHENT1A, data_block_num, card_KEY1, ucArray_ID) != MI_OK)
        {
            printf("PcdAuthState failure\r\n");
            return;
        }
        if (cs == 3) // 只有出入口区的rc522才写入重量
        {
            weight = (int)Get_Weight(); // 获取重量,保存后两位小数
            printf("%d\r\n", weight);
            // 将重量转换为16进制并分配到数组
            read_write_data[0] = (weight >> 8) & 0xFF;              // 高字节
            read_write_data[1] = weight & 0xFF;                     // 低字节
            if (PcdWrite(data_block_num, read_write_data) != MI_OK) // 往数据块写入体重数据
            {
                printf("PcdWrite failure\r\n");
                return;
            }
        }

        string_to_byte_array(cur_time, time_arry, 16); // 将时间转换为16进制并分配到数组

        if (PcdWrite(data_block_num + 1, time_arry) != MI_OK) // 将饮食时间或出入时间写入数据块  21 17
        {
            printf("PcdWrite failure\r\n");
            return;
        }
        memset(read_write_data, 0, sizeof(read_write_data));
        read_animal_info(); // 读取动物基础信息

        // 通过mqtt发送到web管理后台

        // 创建一个JSON对象
        cJSON *json = cJSON_CreateObject();
        sprintf(card_id, "%X%X%X%X", ucArray_ID[0], ucArray_ID[1], ucArray_ID[2], ucArray_ID[3]);

        // // 添加键值对到JSON对象
        cJSON_AddStringToObject(json, "id", card_id);
        cJSON_AddStringToObject(json, "species", special);
        cJSON_AddNumberToObject(json, "age", animal_age);
        cJSON_AddStringToObject(json, "health_status", "health");
        cJSON_AddNumberToObject(json, "weight", weight);
        cJSON_AddStringToObject(json, "eat_time", eat_time);
        cJSON_AddStringToObject(json, "io_time", io_time);

        // 将JSON对象转换为字符串
        char *json_string = cJSON_Print(json);
        // 传输动物体重以及进入时间
        printf("publish_message: %s\r\n", json_string);
        esp_mqtt_client_publish(client, "/animal", json_string, strlen(json_string), 2, 0);
    }
    else
    {
        printf("PcdAnticoll failure\r\n");
    }
}

// 写入动物信息以及读取动物信息
//  扇区7 存放动物信息 年龄 种类 24 25 26 27
//  16体重 17出入口时间 20饮水时间 21进食时间 24年龄 25种类
void rc522_proc2()
{
    // 初始化状态
    uint8_t ucStatusReturn = MI_ERR;

    // 尝试多次寻卡
    for (int attempt = 0; attempt < 3; attempt++)
    {
        ucStatusReturn = PcdRequest(PICC_REQALL, ucArray_ID);
        if (ucStatusReturn == MI_OK)
            break;
    }

    if (ucStatusReturn != MI_OK)
    {
        // printf("PcdRequest failure\r\n");
        RC522_Rese(); // 复位RC522
        return;
    }

    // 防冲突操作
    if (PcdAnticoll(ucArray_ID) == MI_OK)
    {
        // 输出卡ID
        printf("ID: %X %X %X %X\r\n", ucArray_ID[0], ucArray_ID[1], ucArray_ID[2], ucArray_ID[3]);

        // 选卡
        if (PcdSelect(ucArray_ID) != MI_OK)
        {
            printf("PcdSelect failure\r\n");
            return;
        }

        // 校验卡片密码
        if (PcdAuthState(PICC_AUTHENT1A, 24, card_KEY1, ucArray_ID) != MI_OK)
        {
            printf("PcdAuthState failure\r\n");
            return;
        }
        // 写入动物的 种类 年龄 24 25
        string_to_byte_array(special, read_write_data, sizeof(read_write_data)); // 将动物种类转换为16进制并分配到数组
        if (PcdWrite(24, read_write_data) != MI_OK)
        {
            printf("PcdWrite failure\r\n");
            return;
        }
        // 清除数据块24
        memset(read_write_data, 0, sizeof(read_write_data));
        vTaskDelay(10 / portTICK_PERIOD_MS);

        // 将重量转换为16进制并分配到数组
        read_write_data[0] = (animal_age >> 8) & 0xFF; // 高字节
        read_write_data[1] = animal_age & 0xFF;        // 低字节

        if (PcdWrite(25, read_write_data) != MI_OK) // 年龄
        {
            printf("PcdWrite failure\r\n");
            return;
        }
        // 读取数据块24
        if (PcdRead(24, read_write_data) != MI_OK)
        {
            printf("PcdRead failure\r\n");
            return;
        }
        byte_array_to_string(read_write_data, special, sizeof(read_write_data));
        printf("special = %s\r\n", special);
        memset(read_write_data, 0, sizeof(read_write_data)); // 清空数组

        if (PcdRead(25, read_write_data) != MI_OK)
        {
            printf("PcdRead failure\r\n");
            return;
        }
        animal_age = (read_write_data[0] << 8) | read_write_data[1];
        printf("animal_age = %d\r\n", animal_age);

        memset(read_write_data, 0, sizeof(read_write_data)); // 清空数组
        if (PcdAuthState(PICC_AUTHENT1A, 16, card_KEY1, ucArray_ID) != MI_OK)
        {
            printf("PcdAuthState failure\r\n");
            return;
        }
        if (PcdRead(16, read_write_data) != MI_OK)
        {
            printf("PcdRead failure\r\n");
            return;
        }
        weight = (read_write_data[0] << 8) | read_write_data[1];
        printf("weight = %d\r\n", weight);
    }
    else
    {
        printf("PcdAnticoll failure\r\n");
    }
}

esp_err_t wifi_proc()
{
    // wifi连接
    esp_err_t ret;
    ESP_ERROR_CHECK(nvs_flash_init());
    s_wifi_event_group = xEventGroupCreate();

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    esp_netif_t *esp_netif_sta = my_wifi_init_sta();

    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    if (bits & WIFI_CONNECTED_BIT)
    {
        ESP_LOGI(TAG_STA, "connected to ap SSID:%s password:%s", ESP_WIFI_STA_SSID, ESP_WIFI_STA_PASSWD);
    }
    else
    {
        ESP_LOGI(TAG_STA, "Failed to connect to SSID:%s, password:%s", ESP_WIFI_STA_SSID, ESP_WIFI_STA_PASSWD);
    }
    ret = esp_netif_set_default_netif(esp_netif_sta);
    return ret;
}

char *get_current_time() {
    time_t now;
    struct tm *timeinfo;
    static char buffer[80];

    // 获取当前时间
    time(&now);
    timeinfo = localtime(&now);

    // 设置时间格式
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo);

    return buffer;
}

// 将字符串转换为字节数组
void string_to_byte_array(const char *str, uint8_t *byte_array, size_t max_len)
{
    size_t str_len = strlen(str);
    size_t copy_len = (str_len < max_len) ? str_len : max_len;
    memcpy(byte_array, str, copy_len);

    // 如果字符串长度小于最大长度，填充剩余部分为0
    if (copy_len < max_len)
    {
        memset(byte_array + copy_len, 0, max_len - copy_len);
    }
}

// 将字节数组转换为字符串
void byte_array_to_string(const uint8_t *byte_array, char *str, size_t len)
{
    // 复制字节数组到字符串缓冲区
    memcpy(str, byte_array, len);

    // 确保字符串以'\0'结尾
    str[len] = '\0';
}

// 读取动物基础信息
void read_animal_info()
{
    if (PcdAuthState(PICC_AUTHENT1A, 24, card_KEY1, ucArray_ID) != MI_OK)
    {
        printf("PcdAuthState failure\r\n");
        return;
    }

    if (PcdRead(24, read_write_data) != MI_OK) // 读取种类
    {
        printf("PcdRead failure\r\n");
        return;
    }
    byte_array_to_string(read_write_data, special, sizeof(read_write_data));
    printf("special = %s\r\n", special);
    memset(read_write_data, 0, sizeof(read_write_data)); // 清空数组

    if (PcdRead(25, read_write_data) != MI_OK) // 读取年龄
    {
        printf("PcdRead failure\r\n");
        return;
    }
    animal_age = (read_write_data[0] << 8) | read_write_data[1];
    printf("animal_age = %d\r\n", animal_age);

    //读取饮食时间与出入时间
    // 饮食时间 21 出入时间 17
    if (PcdAuthState(PICC_AUTHENT1A, 21, card_KEY1, ucArray_ID) != MI_OK)       //读取饮食时间
    {
        printf("PcdAuthState failure\r\n");
        return;
    }
    if (PcdRead(21, read_write_data) != MI_OK) // 读取种类
    {
        printf("PcdRead failure\r\n");
        return;
    }

    byte_array_to_string(read_write_data, eat_time, sizeof(read_write_data));
    printf("eat_time = %s\r\n", eat_time);
    memset(read_write_data, 0, sizeof(read_write_data)); // 清空数组

    if (PcdAuthState(PICC_AUTHENT1A, 17, card_KEY1, ucArray_ID) != MI_OK)       //读取出入时间
    {
        printf("PcdAuthState failure\r\n");
        return;
    }
    if (PcdRead(17, read_write_data) != MI_OK) // 读取种类
    {
        printf("PcdRead failure\r\n");
        return;
    }

    byte_array_to_string(read_write_data, io_time, sizeof(read_write_data));
    printf("io_time = %s\r\n", io_time);
    memset(read_write_data, 0, sizeof(read_write_data)); // 清空数组
}
